package club.kingyin.kyblog.core.utils.log;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.util.*;

/**
 * <p>整个Log组件的核心类，有以下几个小组件构成<br>
 * {@link ExecutePostProcessor} 方法执行增强，可扩展｜替换原方法，其包含了扩展信息，根据业务生成执行方法结束后的
 * 日志信息传递到后续的日志表，对于方法执行增强所需的参数一般在子类中创建实例注入，子类必须使用以下两个注解
 * {@link org.aspectj.lang.annotation.Aspect}和{@link org.springframework.stereotype.Component}
 * 因为整个组件需要在 Spring 的基础上使用，不能单独使用，
 * 示例：日志{@link LogAspect}，缓存{@link club.kingyin.kyblog.core.utils.cache.CacheAspect}
 * {@link CallExceptionHandler} 异常处理器，在执行方法出现异常时调用
 * {@link BeforePostProcessor} 来源注入，对于网络请求可以注入IP地址
 * {@link LogPostProcessor} 后置增强，对于记录完成的日志可以由此链
 * 发送到异步队列，控制台等等...
 * </p>
 * @author ：kingyin
 * @date ：创建于 2021/8/15 1:41 上午
 * @description ：
 * @modified By：
 * @version: 1.0.0
 */
@Slf4j
public abstract class AbstractLogAspect {

    private final HashMap<Class<? extends LogPostProcessor>, LogPostProcessor> postProcessorHashMap = new HashMap<>();
    private BeforePostProcessor sources;
    private CallExceptionHandler exceptionHandler;
    private final HashMap<Class<? extends ExecutePostProcessor>, ExecutePostProcessor> executePostProcessorHashMap = new HashMap<>();

    /**
     * 切点配置，对接Spring
     */
    protected abstract void pt();

    /**
     * 切点调用，对接Spring
     * @param joinPoint 切点
     * @return 方法调用结果
     * @throws InstantiationException 实例化异常，可能会实例化到一些所需的组件
     * @throws IllegalAccessException
     */
    protected abstract Object logDefault(ProceedingJoinPoint joinPoint) throws InstantiationException, IllegalAccessException;

    public Object log(ProceedingJoinPoint joinPoint, Object handler) throws InstantiationException, IllegalAccessException {
        long begin = System.currentTimeMillis();
        Object res;
        String msg = null;
        // 创建方法处理器
        ExecutePostProcessor executePostProcessor = null;
        // 获得Log注解，在多层嵌套注解也可以获得
        Log log = AnnotatedElementUtils.getMergedAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(), Log.class);
        assert log != null:"在增强的方法中没有找到基本注解 @Log";
        try {
            // 获得当前方法调用时指定的方法处理器
            executePostProcessor = executePostProcessorHashMap.get(log.execute());
            if (executePostProcessor == null) {
                executePostProcessor = log.execute().newInstance();
                executePostProcessorHashMap.put(log.execute(), executePostProcessor);
            }
            // 辅助处理器类型匹配
//            Type handlerType = ((ParameterizedType) executePostProcessor.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

            // 执行指定方法
            res = executePostProcessor.execute(joinPoint, handler);
        } catch (Throwable throwable) {
            // 异常处理
            throwable.printStackTrace();
            msg = throwable.getMessage();
            if (exceptionHandler == null) {
                // 可获得注解覆盖
                exceptionHandler = log
                        .exception().newInstance();
            }
            res = exceptionHandler.afterException(joinPoint);
        }
        long time = System.currentTimeMillis() - begin;
        recordLog(handler, executePostProcessor, joinPoint, log, time, res, msg);
        return res;
    }

    private void recordLog(Object handler, ExecutePostProcessor<?> executePostProcessor, ProceedingJoinPoint point, Log logA, long time, Object res, String msg) throws InstantiationException, IllegalAccessException {
        MethodSignature signature = (MethodSignature) point.getSignature();

        // 创建Log信息
        LogResult result = createResult(signature, logA, time, point, res, msg);

        // 来源属性填充
        if (sources == null) {
            // 缓存同下
            sources = logA.sources().newInstance();
        }
        sources.resourcesSet(result, point);
        // 扩展属性
        result.setExtension(executePostProcessor == null ? null :
                executePostProcessor.afterPostProcessor());
        // 扩展操作
//        TreeSet<Class<? extends LogPostProcessor>>
        Class<? extends LogPostProcessor>[] classes = logA.postProcessor();
        Arrays.sort(classes, (o1, o2) -> {
            Priority o1P = o1.getAnnotation(Priority.class);
            Priority o2P = o2.getAnnotation(Priority.class);
            int p1 = o1P == null ? 0 : o1P.value();
            int p2 = o2P == null ? 0 : o2P.value();
            return p1 - p2;
        });
        for (Class<? extends LogPostProcessor> processor : classes) {
            // 缓存后置处理器，避免浪费内存，不用自动注入的原因是怕麻烦
            LogPostProcessor logPostProcessor = postProcessorHashMap.get(processor);
            if (logPostProcessor == null) {
                logPostProcessor = processor.newInstance();
                postProcessorHashMap.put(processor, logPostProcessor);
            }
            logPostProcessor.PostProcessorLog(result, log, handler);
        }

    }

    private LogResult createResult(MethodSignature signature, Log logAnnotation, long time, ProceedingJoinPoint point, Object res, String msg) {
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        LogResult logResult = new LogResult();
        logResult.setModule(logAnnotation.module());
        logResult.setResult(res);
        logResult.setMethod(className+"."+methodName);
        logResult.setOperator(logAnnotation.operator());
        logResult.setDate(new Date());
        logResult.setTime(time);
        logResult.setParams(point.getArgs());
        logResult.setMsg(msg);
        return logResult;
    }
}
